package com.zhanglinwei.openApiServer.actuator;

import org.apache.ibatis.builder.StaticSqlSource;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static com.zhanglinwei.common.constants.StringPoolConstant.DOT;

@Component
public class DynamicSqlActuator implements InitializingBean {

    @Resource
    private SqlSession sqlSession;

    private MSUtils msUtils;

    public List<Map<String, Object>> selectList(String sql) {
        String msId = this.msUtils.select(sql);
        return this.sqlSession.selectList(msId);
    }

    public List<Map<String, Object>> selectList(String sql, Object params) {
        if (params == null) {
            return selectList(sql);
        }

        String msId = this.msUtils.selectDynamic(sql, params.getClass());
        return this.sqlSession.selectList(msId, params);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.msUtils = new MSUtils(sqlSession.getConfiguration());
    }

    /**
     * 进行预编译
     */
    private static class MSUtils {
        private final Configuration configuration;
        private final LanguageDriver languageDriver;

        private MSUtils(Configuration configuration) {
            this.configuration = configuration;
            this.languageDriver = configuration.getDefaultScriptingLanguageInstance();
        }

        private String newMsId(String sql, SqlCommandType sqlCommandType) {
            return sqlCommandType.toString() + DOT + sql.hashCode();
        }

        private boolean hasMappedStatement(String msId) {
            return this.configuration.hasStatement(msId, false);
        }

        private void newSelectMappedStatement(String msId, SqlSource sqlSource, final Class<?> resultType) {
            MappedStatement ms = (new MappedStatement.Builder(this.configuration, msId, sqlSource, SqlCommandType.SELECT)).resultMaps(new ArrayList<ResultMap>() {
                {
                    this.add((new ResultMap.Builder(MSUtils.this.configuration, "defaultResultMap", resultType, new ArrayList<>(0))).build());
                }
            }).build();
            this.configuration.addMappedStatement(ms);
        }

        private String select(String sql) {
            String msId = this.newMsId(sql, SqlCommandType.SELECT);
            if (!this.hasMappedStatement(msId)) {
                StaticSqlSource sqlSource = new StaticSqlSource(this.configuration, sql);
                this.newSelectMappedStatement(msId, sqlSource, Map.class);
            }
            return msId;
        }

        private String selectDynamic(String sql, Class<?> parameterType) {
            String msId = this.newMsId(sql + parameterType, SqlCommandType.SELECT);
            if (!this.hasMappedStatement(msId)) {
                SqlSource sqlSource = this.languageDriver.createSqlSource(this.configuration, sql, parameterType);
                this.newSelectMappedStatement(msId, sqlSource, Map.class);
            }
            return msId;
        }
    }
}
